// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef UI_TOUCH_SELECTION_TOUCH_SELECTION_CONTROLLER_H_
#define UI_TOUCH_SELECTION_TOUCH_SELECTION_CONTROLLER_H_

#include "base/macros.h"
#include "base/time/time.h"
#include "ui/base/touch/selection_bound.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/geometry/vector2d_f.h"
#include "ui/touch_selection/longpress_drag_selector.h"
#include "ui/touch_selection/selection_event_type.h"
#include "ui/touch_selection/touch_handle.h"
#include "ui/touch_selection/touch_handle_orientation.h"
#include "ui/touch_selection/ui_touch_selection_export.h"

namespace ui {
class MotionEvent;

// Interface through which |TouchSelectionController| issues selection-related
// commands, notifications and requests.
class UI_TOUCH_SELECTION_EXPORT TouchSelectionControllerClient {
public:
    virtual ~TouchSelectionControllerClient() { }

    virtual bool SupportsAnimation() const = 0;
    virtual void SetNeedsAnimate() = 0;
    virtual void MoveCaret(const gfx::PointF& position) = 0;
    virtual void MoveRangeSelectionExtent(const gfx::PointF& extent) = 0;
    virtual void SelectBetweenCoordinates(const gfx::PointF& base,
        const gfx::PointF& extent)
        = 0;
    virtual void OnSelectionEvent(SelectionEventType event) = 0;
    virtual scoped_ptr<TouchHandleDrawable> CreateDrawable() = 0;
};

// Controller for manipulating text selection via touch input.
class UI_TOUCH_SELECTION_EXPORT TouchSelectionController
    : public TouchHandleClient,
      public LongPressDragSelectorClient {
public:
    enum ActiveStatus {
        INACTIVE,
        INSERTION_ACTIVE,
        SELECTION_ACTIVE,
    };

    struct UI_TOUCH_SELECTION_EXPORT Config {
        Config();
        ~Config();

        // Maximum allowed time for handle tap detection. Defaults to 300 ms.
        base::TimeDelta max_tap_duration;

        // Defaults to 8 DIPs.
        float tap_slop;

        // Controls whether adaptive orientation for selection handles is enabled.
        // Defaults to false.
        bool enable_adaptive_handle_orientation;

        // Controls whether drag selection after a longpress is enabled.
        // Defaults to false.
        bool enable_longpress_drag_selection;

        // Controls whether an insertion handle is shown on a tap for an empty
        // editable text. Defauls to false.
        // TODO(mohsen): This flag used to be set to |true| on Aura. That's not the
        // case anymore and it is always |false|. Consider removing it.
        bool show_on_tap_for_empty_editable;
    };

    TouchSelectionController(TouchSelectionControllerClient* client,
        const Config& config);
    ~TouchSelectionController() override;

    // To be called when the selection bounds have changed.
    // Note that such updates will trigger handle updates only if preceded
    // by an appropriate call to allow automatic showing.
    void OnSelectionBoundsChanged(const SelectionBound& start,
        const SelectionBound& end);

    // To be called when the viewport rect has been changed. This is used for
    // setting the state of the handles.
    void OnViewportChanged(const gfx::RectF viewport_rect);

    // Allows touch-dragging of the handle.
    // Returns true iff the event was consumed, in which case the caller should
    // cease further handling of the event.
    bool WillHandleTouchEvent(const MotionEvent& event);

    // To be called before forwarding a tap event. This allows automatically
    // showing the insertion handle from subsequent bounds changes.
    // |tap_count| is tap index in a repeated sequence, i.e., 1 for the first
    // tap, 2 for the second tap, etc...
    bool WillHandleTapEvent(const gfx::PointF& location, int tap_count);

    // To be called before forwarding a longpress event. This allows automatically
    // showing the selection or insertion handles from subsequent bounds changes.
    bool WillHandleLongPressEvent(base::TimeTicks event_time,
        const gfx::PointF& location);

    // To be called before forwarding a gesture scroll begin event to prevent
    // long-press drag.
    void OnScrollBeginEvent();

    // Allow showing the selection handles from the most recent selection bounds
    // update (if valid), or a future valid bounds update.
    void AllowShowingFromCurrentSelection();

    // Hide the handles and suppress bounds updates until the next explicit
    // showing allowance.
    void HideAndDisallowShowingAutomatically();

    // Override the handle visibility according to |hidden|.
    void SetTemporarilyHidden(bool hidden);

    // To be called when the editability of the focused region changes.
    void OnSelectionEditable(bool editable);

    // To be called when the contents of the focused region changes.
    void OnSelectionEmpty(bool empty);

    // Ticks an active animation, as requested to the client by |SetNeedsAnimate|.
    // Returns true if an animation is active and requires further ticking.
    bool Animate(base::TimeTicks animate_time);

    // Returns the rect between the two active selection bounds. If just one of
    // the bounds is visible, the rect is simply the (one-dimensional) rect of
    // that bound. If no selection is active, an empty rect will be returned.
    gfx::RectF GetRectBetweenBounds() const;

    // Returns the visible rect of specified touch handle. For an active insertion
    // these values will be identical.
    gfx::RectF GetStartHandleRect() const;
    gfx::RectF GetEndHandleRect() const;

    // Returns the focal point of the start and end bounds, as defined by
    // their bottom coordinate.
    const gfx::PointF& GetStartPosition() const;
    const gfx::PointF& GetEndPosition() const;

    const SelectionBound& start() const { return start_; }
    const SelectionBound& end() const { return end_; }

    ActiveStatus active_status() const { return active_status_; }

private:
    friend class TouchSelectionControllerTestApi;

    enum InputEventType { TAP,
        REPEATED_TAP,
        LONG_PRESS,
        INPUT_EVENT_TYPE_NONE };

    // TouchHandleClient implementation.
    void OnDragBegin(const TouchSelectionDraggable& draggable,
        const gfx::PointF& drag_position) override;
    void OnDragUpdate(const TouchSelectionDraggable& draggable,
        const gfx::PointF& drag_position) override;
    void OnDragEnd(const TouchSelectionDraggable& draggable) override;
    bool IsWithinTapSlop(const gfx::Vector2dF& delta) const override;
    void OnHandleTapped(const TouchHandle& handle) override;
    void SetNeedsAnimate() override;
    scoped_ptr<TouchHandleDrawable> CreateDrawable() override;
    base::TimeDelta GetMaxTapDuration() const override;
    bool IsAdaptiveHandleOrientationEnabled() const override;

    // LongPressDragSelectorClient implementation.
    void OnLongPressDragActiveStateChanged() override;
    gfx::PointF GetSelectionStart() const override;
    gfx::PointF GetSelectionEnd() const override;

    void ShowInsertionHandleAutomatically();
    void ShowSelectionHandlesAutomatically();
    bool WillHandleTapOrLongPress(const gfx::PointF& location);

    void OnInsertionChanged();
    void OnSelectionChanged();

    // Returns true if insertion mode was newly (re)activated.
    bool ActivateInsertionIfNecessary();
    void DeactivateInsertion();
    // Returns true if selection mode was newly (re)activated.
    bool ActivateSelectionIfNecessary();
    void DeactivateSelection();
    void ForceNextUpdateIfInactive();
    void UpdateHandleLayoutIfNecessary();

    bool WillHandleTouchEventForLongPressDrag(const MotionEvent& event);
    void SetTemporarilyHiddenForLongPressDrag(bool hidden);
    void RefreshHandleVisibility();

    gfx::Vector2dF GetStartLineOffset() const;
    gfx::Vector2dF GetEndLineOffset() const;
    bool GetStartVisible() const;
    bool GetEndVisible() const;
    TouchHandle::AnimationStyle GetAnimationStyle(bool was_active) const;

    void LogSelectionEnd();

    TouchSelectionControllerClient* const client_;
    const Config config_;

    // Whether to force an update on the next selection event even if the
    // cached selection matches the new selection.
    bool force_next_update_;

    InputEventType response_pending_input_event_;

    SelectionBound start_;
    SelectionBound end_;
    TouchHandleOrientation start_orientation_;
    TouchHandleOrientation end_orientation_;

    ActiveStatus active_status_;

    scoped_ptr<TouchHandle> insertion_handle_;
    bool activate_insertion_automatically_;

    scoped_ptr<TouchHandle> start_selection_handle_;
    scoped_ptr<TouchHandle> end_selection_handle_;
    bool activate_selection_automatically_;

    bool selection_empty_;
    bool selection_editable_;

    bool temporarily_hidden_;

    // Whether to use the start bound (if false, the end bound) for computing the
    // appropriate text line offset when performing a selection drag. This helps
    // ensure that the initial selection induced by the drag doesn't "jump"
    // between lines.
    bool anchor_drag_to_selection_start_;

    // Longpress drag allows direct manipulation of longpress-initiated selection.
    LongPressDragSelector longpress_drag_selector_;

    gfx::RectF viewport_rect_;

    base::TimeTicks selection_start_time_;
    // Whether a selection handle was dragged during the current 'selection
    // session' - i.e. since the current selection has been activated.
    bool selection_handle_dragged_;

    DISALLOW_COPY_AND_ASSIGN(TouchSelectionController);
};

} // namespace ui

#endif // UI_TOUCH_SELECTION_TOUCH_SELECTION_CONTROLLER_H_
